#include <linux/errno.h>
#include <linux/fs2.h>
#include <linux/mm_types.h>
#include <linux/byteorder.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/pagemap.h>

#include "internal.h"

static struct inode *romfs_iget(struct super_block *sb, unsigned long pos);

static int romfs_readpage(struct file *file, struct page *page)
{
	struct inode *inode = page->mapping->host;
	loff_t offset, size;
	unsigned long fillsize, pos;
	void *buf;
	int ret;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// buf = kmap(page);
	buf = page_address(page);
	if (!buf)
		return -ENOMEM;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/* 32 bit warning -- but not for us :) */
	offset = page_offset(page);
	size = i_size_read(inode);
	fillsize = 0;
	ret = 0;
	if (offset < size) {
		size -= offset;
		fillsize = size > PAGE_SIZE ? PAGE_SIZE : size;

		pos = ROMFS_I(inode)->i_dataoffset + offset;

		ret = romfs_dev_read(inode->i_sb, pos, buf, fillsize);
		if (ret < 0) {
			// SetPageError(page);
			fillsize = 0;
			ret = -EIO;
		}
	}

	if (fillsize < PAGE_SIZE)
		memset(buf + fillsize, 0, PAGE_SIZE - fillsize);
	// if (ret == 0)
	// 	SetPageUptodate(page);

	// flush_dcache_page(page);
	// kunmap(page);
	// unlock_page(page);
	return ret;
}

static const struct address_space_operations romfs_aops = {
	.readpage	= romfs_readpage
};

int romfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	struct inode *i = filp->f_dentry->d_inode;
	struct romfs_inode ri;
	unsigned long offset, maxoff;
	int j, ino, nextfh;
	int stored = 0;
	char fsname[ROMFS_MAXFN];	/* XXX dynamic? */
	int ret;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	maxoff = romfs_maxsize(i->i_sb);

	// printf("this is %s(): %d\r\n", __func__, __LINE__);

	// offset = filp->f_pos;
	offset = 0;
	if (!offset) {
		// printf("this is %s(): %d\r\n", __func__, __LINE__);
		offset = i->i_ino & ROMFH_MASK;
		ret = romfs_dev_read(i->i_sb, offset, &ri, ROMFH_SIZE);
		if (ret < 0)
			goto out;

		// printf("this is %s(): %d >>> next = 0x%x\r\n", __func__, __LINE__, ri.next);
		// printf("this is %s(): %d >>> spec = 0x%x\r\n", __func__, __LINE__, ri.spec);
		// printf("this is %s(): %d >>> size = 0x%x\r\n", __func__, __LINE__, ri.size);
		// printf("this is %s(): %d >>> checksum = 0x%x\r\n", __func__, __LINE__, ri.checksum);
		// printf("this is %s(): %d >>> name = %s\r\n", __func__, __LINE__, ri.name);

		offset = be32_to_cpu(ri.spec) & ROMFH_MASK;
	}

	// printf("this is %s(): %d\r\n", __func__, __LINE__);

	/* Not really failsafe, but we are read-only... */
	for (;;) {
		// printf("this is %s(): %d\r\n", __func__, __LINE__);
	
		if (!offset || offset >= maxoff) {
			offset = maxoff;
			// filp->f_pos = offset;
			goto out;
		}
		// filp->f_pos = offset;

		/* Fetch inode info */
		ret = romfs_dev_read(i->i_sb, offset, &ri, ROMFH_SIZE);
		if (ret < 0)
			goto out;

		j = romfs_dev_strnlen(i->i_sb, offset + ROMFH_SIZE,
				      sizeof(fsname) - 1);
		if (j < 0)
			goto out;

		ret = romfs_dev_read(i->i_sb, offset + ROMFH_SIZE, fsname, j);
		if (ret < 0)
			goto out;
		fsname[j] = '\0';

		ino = offset;
		nextfh = be32_to_cpu(ri.next);
		if ((nextfh & ROMFH_TYPE) == ROMFH_HRD)
			ino = be32_to_cpu(ri.spec);
		// if (filldir(dirent, fsname, j, offset, ino,
		// 	    romfs_dtype_table[nextfh & ROMFH_TYPE]) < 0)
		// 	goto out;

		printf("this is %s(): %d >>> fsname = %s\r\n", __func__, __LINE__, fsname);
		strcpy(((struct dirents *)dirent)->dirent->d_name, fsname);
		((struct dirents *)dirent)->num++;
		((struct dirents *)dirent)->dirent++;

		stored++;
		offset = nextfh & ROMFH_MASK;
	}

out:
	return stored;
}

static struct dentry *romfs_lookup(struct inode *dir, struct dentry *dentry,
				   struct nameidata *nd)
{
	unsigned long offset, maxoff;
	struct inode *inode;
	struct romfs_inode ri;
	const char *name;		/* got from dentry */
	int len, ret;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	offset = dir->i_ino & ROMFH_MASK;
	ret = romfs_dev_read(dir->i_sb, offset, &ri, ROMFH_SIZE);
	if (ret < 0)
		goto error;

	/* search all the file entries in the list starting from the one
	 * pointed to by the directory's special data */
	maxoff = romfs_maxsize(dir->i_sb);
	offset = be32_to_cpu(ri.spec) & ROMFH_MASK;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	name = dentry->d_name.name;
	len = dentry->d_name.len;

	printf("this is %s(): %d >>> len = %d\r\n", __func__, __LINE__, len);
	printf("this is %s(): %d >>> name = %s\r\n", __func__, __LINE__, name);

	for (;;) {
		if (!offset || offset >= maxoff)
			goto out0;

		ret = romfs_dev_read(dir->i_sb, offset, &ri, sizeof(ri));
		if (ret < 0)
			goto error;

		/* try to match the first 16 bytes of name */
		ret = romfs_dev_strcmp(dir->i_sb, offset + ROMFH_SIZE, name,
				       len);
		if (ret < 0)
			goto error;
		if (ret == 1)
			break;

		/* next entry */
		offset = be32_to_cpu(ri.next) & ROMFH_MASK;
	}

	/* Hard link handling */
	if ((be32_to_cpu(ri.next) & ROMFH_TYPE) == ROMFH_HRD)
		offset = be32_to_cpu(ri.spec) & ROMFH_MASK;

	inode = romfs_iget(dir->i_sb, offset);
	if (IS_ERR(inode)) {
		ret = PTR_ERR(inode);
		goto error;
	}
	goto outi;

	/*
	 * it's a bit funky, _lookup needs to return an error code
	 * (negative) or a NULL, both as a dentry.  ENOENT should not
	 * be returned, instead we need to create a negative dentry by
	 * d_add(dentry, NULL); and return 0 as no error.
	 * (Although as I see, it only matters on writable file
	 * systems).
	 */
out0:
	inode = NULL;
outi:
	d_add(dentry, inode);
	ret = 0;
error:
	return ERR_PTR(ret);
}

static struct inode *romfs_alloc_inode(struct super_block *sb)
{
	struct romfs_inode_info *inode;
	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// inode = kmem_cache_alloc(romfs_inode_cachep, GFP_KERNEL);
	inode = (struct romfs_inode_info *)kmalloc(sizeof(struct romfs_inode_info));
	return inode ? &inode->vfs_inode : NULL;
}

static const struct file_operations romfs_dir_operations = {
	// .read		= generic_read_dir,
	.readdir	= romfs_readdir,
};

static const struct inode_operations romfs_dir_inode_operations = {
	.lookup		= romfs_lookup,
};

static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
{
	struct romfs_inode_info *inode;
	struct romfs_inode ri;
	struct inode *i;
	unsigned long nlen;
	unsigned nextfh;
	int ret;
	// umode_t mode;

	/* we might have to traverse a chain of "hard link" file entries to get
	 * to the actual file */
	for (;;) {
		ret = romfs_dev_read(sb, pos, &ri, sizeof(ri));
		if (ret < 0)
			goto error;

		/* XXX: do romfs_checksum here too (with name) */

		nextfh = be32_to_cpu(ri.next);
		if ((nextfh & ROMFH_TYPE) != ROMFH_HRD)
			break;

		pos = be32_to_cpu(ri.spec) & ROMFH_MASK;
		// pos = nextfh & ROMFH_MASK;
	}

	/* determine the length of the filename */
	nlen = romfs_dev_strnlen(sb, pos + ROMFH_SIZE, ROMFS_MAXFN);
	if (IS_ERR_VALUE(nlen))
		goto eio;

	/* get an inode for this image position */
	i = iget_locked(sb, pos);
	if (!i)
		return ERR_PTR(-ENOMEM);

	// if (!(i->i_state & I_NEW))
	// 	return i;

	/* precalculate the data offset */
	inode = ROMFS_I(i);
	inode->i_metasize = (ROMFH_SIZE + nlen + 1 + ROMFH_PAD) & ROMFH_MASK;
	inode->i_dataoffset = pos + inode->i_metasize;

	i->i_nlink = 1;		/* Hard to decide.. */
	i->i_size = be32_to_cpu(ri.size);
	// i->i_mtime.tv_sec = i->i_atime.tv_sec = i->i_ctime.tv_sec = 0;
	// i->i_mtime.tv_nsec = i->i_atime.tv_nsec = i->i_ctime.tv_nsec = 0;

	/* set up mode and ops */
	// mode = romfs_modemap[nextfh & ROMFH_TYPE];

	switch (nextfh & ROMFH_TYPE) {
	case ROMFH_DIR:
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		i->i_size = ROMFS_I(i)->i_metasize;
		i->i_op = &romfs_dir_inode_operations;
		i->i_fop = &romfs_dir_operations;
		// if (nextfh & ROMFH_EXEC)
		// 	mode |= S_IXUGO;
		break;
	case ROMFH_REG:
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		i->i_fop = &romfs_ro_fops;
		i->i_data.a_ops = &romfs_aops;
		// if (i->i_sb->s_mtd)
		// 	i->i_data.backing_dev_info =
		// 		i->i_sb->s_mtd->backing_dev_info;
		// if (nextfh & ROMFH_EXEC)
		// 	mode |= S_IXUGO;
		break;
	case ROMFH_SYM:
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// i->i_op = &page_symlink_inode_operations;
		i->i_data.a_ops = &romfs_aops;
		// mode |= S_IRWXUGO;
		break;
	default:
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		/* depending on MBZ for sock/fifos */
		// nextfh = be32_to_cpu(ri.spec);
		// init_special_inode(i, mode, MKDEV(nextfh >> 16,
		// 				  nextfh & 0xffff));
		break;
	}

	// i->i_mode = mode;

	// unlock_new_inode(i);
	return i;

eio:
	ret = -EIO;
error:
	printf("ROMFS: read error for inode 0x%lx\r\n", pos);
	return ERR_PTR(ret);
}

static const struct super_operations romfs_super_ops = {
	.alloc_inode	= romfs_alloc_inode,
	// .destroy_inode	= romfs_destroy_inode,
	// .statfs		= romfs_statfs,
	// .remount_fs	= romfs_remount,
};

static __u32 romfs_checksum(const void *data, int size)
{
	const __be32 *ptr = data;
	__u32 sum;

	sum = 0;
	size >>= 2;
	while (size > 0) {
		sum += be32_to_cpu(*ptr++);
		size--;
	}
	return sum;
}

static int romfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct romfs_super_block *rsb;
	struct inode *root;
	unsigned long pos, img_size;
	const char *storage;
	size_t len;
	int ret;

// #ifdef CONFIG_BLOCK
	// if (!sb->s_mtd) {
		// sb_set_blocksize(sb, ROMBSIZE);
	// } else {
		sb->s_blocksize = ROMBSIZE;
		// sb->s_blocksize_bits = blksize_bits(ROMBSIZE);
	// }
// #endif

	sb->s_maxbytes = 0xFFFFFFFF;
	sb->s_magic = ROMFS_MAGIC;
	// sb->s_flags |= MS_RDONLY | MS_NOATIME;
	sb->s_op = &romfs_super_ops;

	/* read the image superblock and check it */
	rsb = kmalloc(512);
	if (!rsb)
		return -ENOMEM;

	sb->s_fs_info = (void *) 512;
	ret = romfs_dev_read(sb, 0, rsb, 512);
	if (ret < 0)
		goto error_rsb;

	printf("romfs_fill_super(): rsb->word0 = %x\r\n", rsb->word0);
	printf("romfs_fill_super(): rsb->word1 = %x\r\n", rsb->word1);
	printf("romfs_fill_super(): rsb->size = %x\r\n", rsb->size);
	printf("romfs_fill_super(): rsb->checksum = %x\r\n", rsb->checksum);
	printf("romfs_fill_super(): rsb->name = %s\r\n", rsb->name);

	img_size = be32_to_cpu(rsb->size);

	// if (sb->s_mtd && img_size > sb->s_mtd->size)
	// 	goto error_rsb_inval;

	sb->s_fs_info = (void *) img_size;

	if (rsb->word0 != ROMSB_WORD0 || rsb->word1 != ROMSB_WORD1 ||
	    img_size < ROMFH_SIZE) {
		if (!silent)
			printf("VFS:"
			       " Can't find a romfs filesystem on dev.\r\n");
		goto error_rsb_inval;
	}

	if (romfs_checksum(rsb, min_t(size_t, img_size, 512))) {
		printf("ROMFS: bad initial checksum on dev.\r\n");
		// goto error_rsb_inval;
	}

	// storage = sb->s_mtd ? "MTD" : "the block layer";

	len = strnlen(rsb->name, ROMFS_MAXFN);
	if (!silent)
		printf("ROMFS: Mounting image '%d.%s' through %s\r\n",
		       (unsigned) len, rsb->name, "the block layer");

	// kfree(rsb);
	rsb = NULL;

	/* find the root directory */
	pos = (ROMFH_SIZE + len + 1 + ROMFH_PAD) & ROMFH_MASK;

#if 1
	struct romfs_inode ri;
	const char *name = "root";
	int dirnamelen = strlen("root");
	for (;;) {
		ret = romfs_dev_read(sb, pos, &ri, sizeof(ri));
		if (ret < 0)
			goto error;

		/* try to match the first 16 bytes of name */
		ret = romfs_dev_strcmp(sb, pos + ROMFH_SIZE, name,
				       dirnamelen);
		if (ret < 0)
			goto error;
		if (ret == 1)
			break;

		/* next entry */
		pos = be32_to_cpu(ri.next) & ROMFH_MASK;
	}
#endif
	root = romfs_iget(sb, pos);
	if (IS_ERR(root))
		goto error;

	sb->s_root = d_alloc_root(root);
	if (!sb->s_root)
		goto error_i;

	return 0;

error_i:
	// iput(root);
error:
	return -EINVAL;
error_rsb_inval:
	ret = -EINVAL;
error_rsb:
	return ret;
}

static int romfs_get_sb(struct file_system_type *fs_type,
			int flags, const char *dev_name,
			void *data, struct vfsmount *mnt)
{
	int ret = -EINVAL;

	printf("this is %s(): %d\r\n", __func__, __LINE__);
// #ifdef CONFIG_ROMFS_ON_MTD
// 	ret = get_sb_mtd(fs_type, flags, dev_name, data, romfs_fill_super,
// 			 mnt);
// #endif
// #ifdef CONFIG_ROMFS_ON_BLOCK
	if (ret == -EINVAL)
		ret = get_sb_bdev(fs_type, flags, dev_name, data,
				  romfs_fill_super, mnt);
// #endif
	return ret;
}

static struct file_system_type romfs_fs_type = {
	// .owner		= THIS_MODULE,
	.name		= "romfs",
	.get_sb		= romfs_get_sb,
	// .kill_sb	= romfs_kill_sb,
	// .fs_flags	= FS_REQUIRES_DEV,
};

int init_romfs_fs(void)
{
	int ret;

	// printf("ROMFS MTD (C) 2007 Red Hat, Inc.\r\n");

	// romfs_inode_cachep =
	// 	kmem_cache_create("romfs_i",
	// 			  sizeof(struct romfs_inode_info), 0,
	// 			  SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD,
	// 			  romfs_i_init_once);

	// if (!romfs_inode_cachep) {
	// 	printk(KERN_ERR
	// 	       "ROMFS error: Failed to initialise inode cache\n");
	// 	return -ENOMEM;
	// }
	ret = register_filesystem(&romfs_fs_type);
	if (ret) {
		printf("ROMFS error: Failed to register filesystem\r\n");
		goto error_register;
	}
	return 0;

error_register:
	// kmem_cache_destroy(romfs_inode_cachep);
	return ret;
}
